﻿//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Microsoft Public License.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace AstoriaOverAstoria
{
    using System;
    using System.Diagnostics;
    using System.Linq.Expressions;

    /// <summary>Base class for all visitors which convert expressions.</summary>
    /// <remarks>Provides common store for annotations and metadata information.
    /// Also adds the ability to store <see cref="IQueryResultsProcessor"/> instances to be applied to the expression results.</remarks>
    internal abstract class ExpressionConverter : ResourceAnnotationPreservingExpressionVisitor
    {
        /// <summary>The conversion result so far.</summary>
        private ExpressionConversionResult result;

        /// <summary>The metadata mapping used for this expression converter.</summary>
        private MetadataMapping metadataMapping;

        /// <summary>Constructor</summary>
        /// <param name="annotations">The resource annotations for the expressions being processed.</param>
        /// <param name="metadataMapping">The metadata mapping for the service this expression applies to.</param>
        public ExpressionConverter(ExpressionResourceAnnotations annotations, MetadataMapping metadataMapping)
            : base(annotations)
        {
            Debug.Assert(metadataMapping != null, "metadataMapping != null");
            this.metadataMapping = metadataMapping;
        }

        /// <summary>The conversion result so far.</summary>
        protected ExpressionConversionResult Result
        {
            get { return this.result; }
        }

        /// <summary>The metadata mapping used for this converter service.</summary>
        protected MetadataMapping MetadataMapping
        {
            get { return this.metadataMapping; }
        }

        /// <summary>Converts specified expression from the server generated expression to the expression understood by the client LINQ.</summary>
        /// <param name="expression">The expression to process.</param>
        /// <param name="annotations">The resource annotations for the expression.</param>
        /// <param name="metadataMapping">The metadata mapping for the service this expression applies to.</param>
        /// <returns>The conversion result containing the converted expression as well as query results processor to apply to the results.</returns>
        public static ExpressionConversionResult Convert(Expression expression, ExpressionResourceAnnotations annotations, MetadataMapping metadataMapping)
        {
            Debug.Assert(annotations != null, "annotations != null");
            Debug.Assert(metadataMapping != null, "metadataMapping != null");

            // Determine annotations and convert to CLR property access
            expression = ResourceAnnotationVisitor.Convert(expression, annotations);

            // Apply conversions
            ExpressionConversionResult result = ConvertInnerExpression(expression, annotations, metadataMapping);

            // Simplify the expressions (cleanup after some of the conversions)
            expression = ExpressionSimplifier.Simplify(result.Expression);

            result.SetResultExpression(expression);
            return result;
        }

        /// <summary>Visits a method call expression.</summary>
        /// <param name="m">The method call expression.</param>
        /// <returns>The visited expression.</returns>
        internal override Expression VisitMethodCall(MethodCallExpression m)
        {
            return base.VisitMethodCall(m);
        }

        /// <summary>Runs the conversion and generates a new result.</summary>
        /// <param name="source">The source conversion result to start with.</param>
        /// <returns>The new conversion result.</returns>
        protected ExpressionConversionResult Convert(ExpressionConversionResult source)
        {
            this.result = new ExpressionConversionResult(source);
            Expression expression = this.Visit(source.Expression);
            this.result.SetResultExpression(expression);
            return this.result;
        }

        /// <summary>Helper method to visit <see cref="MethodCallExpression"/> but don't visit projections as usually the projections
        /// mean different metadata/query shape.</summary>
        /// <param name="m">The method call to visit.</param>
        /// <returns>The visited expression.</returns>
        /// <remarks>Most expression convertes will want to call this instead of the base.VisitMethodCall
        /// since visting projections as well might screw up the query result processors.</remarks>
        protected Expression VisitMethodCallSkipProjections(MethodCallExpression m)
        {
            var selectMatch = ExpressionUtil.MatchSelectCall(m);
            if (selectMatch != null)
            {
                Expression source = this.Visit(selectMatch.Source);
                return this.Annotations.PropagateResourceType(
                    selectMatch.Lambda,
                    Expression.Call(selectMatch.MethodCall.Method, source, selectMatch.Lambda));
            }
            else
            {
                return base.VisitMethodCall(m);
            }
        }

        /// <summary>Applies conversions to an inner expression. This does not apply simplifications and it assumes annotations are already setup.</summary>
        /// <param name="expression">The expression to convert.</param>
        /// <returns>The conversion result.</returns>
        protected ExpressionConversionResult ConvertInnerExpression(Expression expression)
        {
            return ConvertInnerExpression(expression, this.Annotations, this.metadataMapping);
        }

        /// <summary>Applies conversions to an inner expression. This does not apply simplifications and it assumes annotations are already setup.</summary>
        /// <param name="expression">The expression to convert.</param>
        /// <param name="annotations">The resource annotations for the expression.</param>
        /// <param name="contextMapping">The context mapping - the metadata to use.</param>
        /// <returns>The conversion result.</returns>
        private static ExpressionConversionResult ConvertInnerExpression(
            Expression expression,
            ExpressionResourceAnnotations annotations,
            MetadataMapping contextMapping)
        {
            Debug.Assert(expression != null, "expression != null");
            Debug.Assert(annotations != null, "annotations != null");
            Debug.Assert(contextMapping != null, "contextMapping != null");

            ExpressionConversionResult result = new ExpressionConversionResult(expression);

            // Apply property navigation conversion
            PropertyNavigationConverter propertyNavigationConverter = new PropertyNavigationConverter(annotations, contextMapping);
            result = propertyNavigationConverter.Convert(result);

            // Apply filter conversion
            FilterConverter filterConverter = new FilterConverter(annotations, contextMapping);
            result = filterConverter.Convert(result);

            // Apply projection conversion
            ProjectionConverter projectionConverter = new ProjectionConverter(annotations, contextMapping);
            result = projectionConverter.Convert(result);

            return result;
        }
    }
}
